package net.melon.uploader;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import net.esj.basic.core.engine.config.MelonConfig;
import net.esj.basic.core.view.tools.ActionContext;
import net.esj.basic.utils.StringUtils;
import net.esj.basic.utils.UUIDCreator;
import net.esj.basic.core.utils.PropertiesInfo;
import net.melon.uploader.exception.UploadRuntimeException;
import net.melon.uploader.listener.CountPercentListener;
import net.melon.uploader.pojo.FileUpload;
import net.melon.uploader.service.FileNameRule;
import net.melon.uploader.service.PathRule;
import net.melon.uploader.service.UploaderService;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.FileUtils;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;

public class Uploader {

	public static final String SYS_UPLOAD_COUNTER_SESSION_KEY = "_ml_upload_counter_sk_";
	
	/**
	 * 默认目标上传根路径
	 */
	public static final String SYS_UPLOADER_SAVE_PATH = "sys.uploader.save.path";

	/**
	 * 默认下载根路径
	 */
	public static final String SYS_UPLOADER_DOWNLOAD_URL = "sys.uploader.download.url";

	public static final String FILE_DESCRIPTION_UPLOAD_KEY = "_uploader_file_description";
	
	public static final String[] ALLOW_FILE_IMAGES = {".jpg",".gif",".png",".jpeg",".bmp"}; 
	
	public static final String[] ALLOW_FILE_EVERYTHING = {".*"};

	// 输出文件地址
	private String url = "";
	// 上传文件名
	private String fileName = "";
	// 文件类型
	private String type = "";
	// 原始文件名
	private String originalName = "";
	// 文件大小
	private String size = "";

	private HttpServletRequest request = null;
	private String title = "";

	// 文件允许格式
	private String[] allowFiles = { ".rar", ".doc", ".docx", ".zip", ".pdf",
			".txt", ".swf", ".wmv", ".gif", ".png", ".jpg", ".jpeg", ".bmp" };
	// 文件大小限制，单位KB
	private int maxSize = 10000;

	private PathRule pathRule;

	private FileNameRule fileNameRule;

	private String subPathKey;

	private Object[] args;

	private String[] fieldsName = new String[0];
	
	/**
	 * FIXME: 完善： 1）增加数据库信息保存 2）增加额外信息保存 3）是否要删除原数据
	 * 
	 * @param pathRule 上传路径规则
	 * @param fileNameRule 上传文件名规则
	 * @param subPathKey 上传子路径
	 * @param args 额外参数
	 */
	public Uploader(PathRule pathRule, FileNameRule fileNameRule,
			String subPathKey, Object... args) {
		this.pathRule = pathRule;
		this.fileNameRule = fileNameRule;
		this.subPathKey = subPathKey;
		this.args = args;
	}
	
	public Uploader(PathRule pathRule, FileNameRule fileNameRule,
			String subPathKey,String[] fieldsName, Object... args) {
		this.pathRule = pathRule;
		this.fileNameRule = fileNameRule;
		this.subPathKey = subPathKey;
		this.args = args;
		this.fieldsName = fieldsName;
	}

	public List<FileUpload> upload(HttpServletRequest request) throws Exception {
		this.request = request;
		boolean isMultipart = ServletFileUpload
				.isMultipartContent(this.request);
		if (!isMultipart) {
			throw new UploadRuntimeException(ErrorInfo.ERR_ENTYPE);
		}
		List<FileUpload> list = new ArrayList<FileUpload>();
		if (request instanceof MultiPartRequestWrapper) {
			MultiPartRequestWrapper mulreq = ((MultiPartRequestWrapper) request);
			List<String> pnames = new ArrayList<String>();
			Enumeration<String> fileParameterNames = mulreq
					.getFileParameterNames();
			while (fileParameterNames != null
					&& fileParameterNames.hasMoreElements()) {
				String pname = fileParameterNames.nextElement();
				if(!checkFieldsName(pname)){
					continue;
				}
				String[] names = mulreq.getFileNames(pname);
				for (String name : names) {
					if (!this.checkFileType(name)) {
						throw new UploadRuntimeException(ErrorInfo.ERR_TYPE);
					}
				}
				pnames.add(pname);
			}
			for (String pname : pnames) {
				File[] files = mulreq.getFiles(pname);
				String[] names = mulreq.getFileNames(pname);
				for (int i = 0; i < files.length; i++) {
					File nfile = new File(getFileRealName(files[i], names[i],
							args));
					FileUtils.copyFile(files[i], nfile);
					FileUpload fu = new FileUpload();
					fu.setKey(pname);
					fu.setOriginal(names[i]);
					fu.setUrl(nfile.getAbsolutePath().replace(getRootDir(subPathKey), "")
							.replace(File.separator, "/"));
					list.add(fu);
				}
			}
		} else {
			DiskFileItemFactory dff = new DiskFileItemFactory();
			dff.setRepository(new File(getRootDir(subPathKey)));
			try {
				ServletFileUpload sfu = new ServletFileUpload(dff);
				sfu.setSizeMax(this.maxSize * 1024);
				sfu.setHeaderEncoding("utf-8");
				sfu.setProgressListener(new CountPercentListener(request.getSession()));
				FileItemIterator fii = sfu.getItemIterator(this.request);
				List<ItemStream> allowedItems = new ArrayList<ItemStream>();
				while (fii.hasNext()) {
					FileItemStream fis = fii.next();
					String fieldName = fis.getFieldName();
					if(!checkFieldsName(fieldName)){
						continue;
					}
					if (!fis.isFormField()) {
						String originalName = fis.getName().substring(fis.getName().lastIndexOf(System.getProperty("file.separator")) + 1);//文件原始名称
						if (!this.checkFileType(originalName)) {
							throw new UploadRuntimeException(ErrorInfo.ERR_TYPE);
						}
						ItemStream is = new ItemStream();
						is.setStream(fis);
						is.setFieldName(fieldName);
						allowedItems.add(is);
						// UE中只会处理单张上传，完成后即退出
						// break;
					} else {
						String fname = fis.getFieldName();
						// 只处理title，其余表单请自行处理
						if (!fname.equals(FILE_DESCRIPTION_UPLOAD_KEY)) {
							continue;
						}
						BufferedInputStream in = new BufferedInputStream(
								fis.openStream());
						BufferedReader reader = new BufferedReader(
								new InputStreamReader(in));
						StringBuffer result = new StringBuffer();
						while (reader.ready()) {
							result.append((char) reader.read());
						}
						this.title = new String(result.toString().getBytes(),
								"utf-8");
						reader.close();

					}
				}
				for (ItemStream is : allowedItems) {
					FileItemStream fis = is.getStream();
					if(fis==null){
						continue;
					}
					String originalName = fis.getName().substring(
							fis.getName().lastIndexOf(
									System.getProperty("file.separator")) + 1);
					String tempfileName = createTempName(originalName);
					String tempPath = getRootDir(subPathKey) + tempfileName;
					BufferedInputStream in = new BufferedInputStream(
							fis.openStream());
					System.out.println("fis.openStream().available():"+fis.openStream().available());
					File tempFile = new File(tempPath);
					FileOutputStream out = new FileOutputStream(tempFile);
					BufferedOutputStream output = new BufferedOutputStream(out);
					Streams.copy(in, output, true);
					String fileRealName = getFileRealName(tempFile,
							originalName, args);
					FileUtils.copyFile(tempFile, new File(fileRealName));
					FileUpload fu = new FileUpload();
					fu.setOriginal(originalName);
					fu.setKey(is.getFieldName());
					fu.setUrl(fileRealName.replace(getRootDir(subPathKey), "")
							.replace(File.separator, "/"));
					list.add(fu);
				}

			} catch (SizeLimitExceededException e) {
				throw new UploadRuntimeException(ErrorInfo.OVER_SIZE);
			} catch (InvalidContentTypeException e) {
				throw new UploadRuntimeException(ErrorInfo.ERR_ENTYPE);
			} catch (FileUploadException e) {
				throw new UploadRuntimeException(ErrorInfo.ERR_REQUEST);
			} catch (Exception e) {
				throw new UploadRuntimeException(ErrorInfo.UNKNOW);
			}finally{
				request.getSession().removeAttribute(SYS_UPLOAD_COUNTER_SESSION_KEY);
			}
		}
		return list;
	}

	public static String getDownUrl(String subPathKey) {
		String base = MelonConfig.getProperty(SYS_UPLOADER_DOWNLOAD_URL);
		if (!StringUtils.hasText(base)) {
			base = MelonConfig
					.getProperty(PropertiesInfo.MELON_PROP_SYS_DOWNLOAD_URL);
			if (!StringUtils.hasText(base)) {
				base = "http://" + ActionContext.getRequest().getServerName() + ":"
						+ ActionContext.getRequest().getServerPort() + "/"
						+ ((HttpServletRequest)ActionContext.getRequest()).getContextPath() + "/";
			}
		}
		if (!base.endsWith("/")) {
			base += "/";
		}
		base += UploaderService.getPath(subPathKey)
				.replace(File.separator, "/");
		if (!base.endsWith("/")) {
			base += "/";
		}
		return base;
	}

	public static UploadCounter getCounter(){
		return (UploadCounter) ActionContext.getContext().getSession().getAttribute(SYS_UPLOAD_COUNTER_SESSION_KEY);
	}
	
	public String[] getDownFiles() {
		List<File> files = getAllFiles(getRootDir(subPathKey));
		String[] paths = new String[files.size()];
		for (int i = 0; i < files.size(); i++) {
			File f = files.get(i);
			paths[i] = f.getAbsolutePath().replace(getRootDir(subPathKey), "")
					.replace(File.separator, "/");
		}
		return paths;
	}

	private List<File> getAllFiles(String path) {
		List<File> files = new ArrayList<File>();
		File realFile = new File(path);
		if (realFile.isDirectory()) {
			File[] subfiles = realFile.listFiles();
			for (File file : subfiles) {
				if (file.isDirectory()) {
					files.addAll(getAllFiles(file.getAbsolutePath()));
				} else {
					if (checkFileType(file.getName())) {
						files.add(file);
					}
				}
			}
		}
		return files;
	}

	/**
	 * 接受并保存以base64格式上传的文件
	 * 
	 * @param fieldName
	 */
	/*
	 * public void uploadBase64(String fieldName) { String savePath =
	 * this.getFolder(this.savePath); String base64Data =
	 * this.request.getParameter(fieldName); this.fileName =
	 * this.getName("test.png"); this.url = savePath + "/" + this.fileName;
	 * BASE64Decoder decoder = new BASE64Decoder(); try { File outFile = new
	 * File(this.getPhysicalPath(this.url)); OutputStream ro = new
	 * FileOutputStream(outFile); byte[] b = decoder.decodeBuffer(base64Data);
	 * for (int i = 0; i < b.length; ++i) { if (b[i] < 0) { b[i] += 256; } }
	 * ro.write(b); ro.flush(); ro.close(); this.state =
	 * this.errorInfo.get("SUCCESS"); } catch (Exception e) { this.state =
	 * this.errorInfo.get("IO"); } }
	 */

	/**
	 * 文件类型判断
	 * 
	 * @param fileName
	 * @return
	 */
	private boolean checkFileType(String fileName) {
		Iterator<String> type = Arrays.asList(this.allowFiles).iterator();
		String matchs = StringUtils.join(allowFiles, "|").toLowerCase();
		return Pattern.matches(matchs, getFileExt(fileName.toLowerCase()));
		/*while (type.hasNext()) {
			String ext = type.next();
			if(org.apache.commons.lang.StringUtils.equals(".*", ext)){//正则测试下
				return true;
			}
			if (fileName.toLowerCase().endsWith(ext)) {
				return true;
			}
		}
		return false;*/
	}

	/**
	 * 获取文件扩展名
	 * 
	 * @return string
	 */
	private String getFileExt(String fileName) {
		return fileName.substring(fileName.lastIndexOf("."));
	}

	/**
	 * 依据原始文件名生成新文件名
	 * 
	 * @return
	 */
	private String createTempName(String fileName) {
		return UUIDCreator.create() + "_" + System.currentTimeMillis()
				+ this.getFileExt(fileName);
	}

	/**
	 * 根据字符串创建本地目录 并按照日期建立子目录返回
	 * 
	 * @param path
	 * @return
	 */
	private String getFolder(final File file, final String filename,
			final Object... args) {
		File dir = new File(this.getPhysicalPath(file, filename, args));
		if (!dir.exists()) {
			try {
				dir.mkdirs();
			} catch (Exception e) {
				throw new UploadRuntimeException(ErrorInfo.ERR_DIR);
			}
		}
		return dir.getPath();
	}

	private String getFileRealName(File file, String filename, Object... args) {
		StringBuilder sb = new StringBuilder();
		sb.append(getPhysicalPath(file, filename, args)).append(
				getFileName(file, filename, args));
		return sb.toString();
	}

	/**
	 * 根据传入的虚拟路径获取物理路径
	 * 
	 * @param path
	 * @return
	 */
	private String getPhysicalPath(File file, String filename, Object... args) {
		StringBuilder sb = new StringBuilder();
		String subpath = pathRule.getRelativePath(file, filename, args);
		if (!subpath.endsWith(File.separator)) {
			subpath += File.separator;
		}
		sb.append(getRootDir(subPathKey)).append(subpath);
		// .append(getFileName(file, filename, args));
		return sb.toString();
	}
	
	public static final String getRootDir(String subPathKey){
		String baseDir = MelonConfig.getProperty(SYS_UPLOADER_SAVE_PATH);
		if (!StringUtils.hasText(baseDir)) {
			baseDir = MelonConfig
					.getProperty(PropertiesInfo.MELON_PROP_SYS_UPLOAD_ROOT);
			if (!StringUtils.hasText(baseDir)) {
				/*String servletPath = this.request.getServletPath();
				baseDir = this.request.getSession().getServletContext()
						.getRealPath(servletPath);*/
				throw new UploadRuntimeException(ErrorInfo.ERR_CONFIG);
			}
		}
		if (!baseDir.endsWith(File.separator)) {
			baseDir += File.separator;
		}
		String modelPath = UploaderService.getPath(subPathKey);
		if (!modelPath.endsWith(File.separator)) {
			modelPath += File.separator;
		}
		baseDir += modelPath;
		File dir = new File(baseDir);
		if (!dir.exists()) {
			dir.mkdirs();
		}
		return baseDir;
	}

	/*
	 * private String getBaseDownUrl(){
	 * 
	 * }
	 */

	private String getFileName(File file, String filename, Object... args) {
		return fileNameRule.getFileName(file, filename, args);
	}
	
	private boolean checkFieldsName(String name){
		if(fieldsName.length==0){
			return true;
		}
		for(String str : fieldsName){
			if(org.apache.commons.lang.StringUtils.equals(str, name)){
				return true;
			}
		}
		return false;
	}

	public void setAllowFiles(String[] allowFiles) {
		this.allowFiles = allowFiles;
	}

	public void setMaxSize(int size) {
		this.maxSize = size;
	}

	public String getSize() {
		return this.size;
	}

	public String getUrl() {
		return this.url;
	}

	public String getFileName() {
		return this.fileName;
	}

	public String getTitle() {
		return this.title;
	}

	public String getType() {
		return this.type;
	}

	public String getOriginalName() {
		return this.originalName;
	}
	
	private static class ItemStream{
		private String fieldName;
		private FileItemStream stream;
		public String getFieldName() {
			return fieldName;
		}
		public void setFieldName(String fieldName) {
			this.fieldName = fieldName;
		}
		public FileItemStream getStream() {
			return stream;
		}
		public void setStream(FileItemStream stream) {
			this.stream = stream;
		}
		
	}
}
